package com.retrozelda.nyanimals;

import java.util.Date;
import java.util.Hashtable;
import java.util.Random;

import com.retrozelda.nyanimals.Animations.CAnimation;
import com.retrozelda.nyanimals.INyanObject.Type;

import android.graphics.RectF;
import android.util.Log;

public class CNyanimal extends CRectangle
{
	private static final String TAG = CNyanimal.class.getSimpleName();
	

	
	// ////////////////////////////////////////////////////////
	// ////////////////////////////////////////////////////////

	// ////////// Nyanimal Actions/Status /////////////////////
	// ////////////////////////////////////////////////////////
	enum NyanimalStates { IDLE, EATING, SLEEPING, WALKING, TOTAL };
	enum NyanimalStatus { HAPPY, HUNGRY, SLEEPY, BORED, SICK, DEAD }; // determines different "idle" animations
	enum NyanimalStages 
	{ 
		EGG, BABY, CHILD, TEEN, ADULT, SENIOR; 
		
		// gets the age that the particular stage starts
		public int GetStartAge()
		{
			switch(this)
			{
			case EGG:
				return -1;
			case BABY:
				return 0;
			case CHILD:
				return 5;
			case TEEN:
				return 13;
			case ADULT:
				return 25;
			case SENIOR:
				return 65;
			}		
			return -2;
		}		
	};
	
	// NOTE: if you add here, you need to add in clone()
	NyanimalStates	m_curState;
	NyanimalStatus	m_curStatus;
	NyanimalStages	m_curStage;
	Date			m_birthDay;
	char			m_curAge;		// in days
	
	// NOTE: when anything is < 0 the happy meter goes down, and everything > 0 makes the meter go up
	// happy meter goes down faster with multiple problems, and goes up at a constant rate
	float			m_happyMeter;	// 100 is happy,	 0 is unhappy,		// -100 mean dead
	float			m_healthMeter;	// 100 is healthy,	 0 is unhealthy,	// medicine brings to 100
	float			m_hungerMeter;	// 100 is full,		 0 is hungry,		// goes down slower while sleeping
	float			m_tiredMeter;	// 100 is sleepy, 	 0 is wide awake,	// sleep brings to 100
	float			m_boredMeter;	// 100 is bored,	 0 is content,		
	
	float GetmeterMin() { return  0.0f;}
	float GetMeterMax() { return  100.0f;}
	float GetHappyMeter() { return m_happyMeter;}
	float GetHealthMeter() { return m_healthMeter;}
	float GetHungerMeter() { return m_hungerMeter;}
	float GetTiredMeter() { return m_tiredMeter;}
	float GetBoredMeter() { return m_boredMeter;}

	// ////////////////////////////////////////////////////////
	// ////////////////////////////////////////////////////////

	// ///////////////// animations/movement ///////////////////////////
	// ////////////////////////////////////////////////////////
	enum NyanimalAnimations { IDLE, EATING, SLEEPING, 
		WALKING_UP, WALKING_DOWN, WALKING_LEFT, WALKING_RIGHT, 
		HUNGRY, SLEEPY, BORED, SICK, DEAD};
		
	enum Direction { 
		UP, DOWN, LEFT, RIGHT;
		static Direction GetDirectionByValue(int val)
		{
			int dir = val % 4; // safety filter
			switch(dir)
			{
			case 0:
				return UP;
			case 1:
				return DOWN;
			case 2:
				return LEFT;
			case 3:
				return RIGHT;
			}
			return DOWN;
		}
	};
	
	
	CRectangle m_Shadow;
	boolean m_bMirror;
	Random m_random;
	Hashtable<NyanimalAnimations, _AnimationTextureLink> m_Animations;
	NyanimalAnimations m_curAnimation;
	Direction m_curDirection;
	float m_walkSpeed;
	float m_timeInState;
	int m_destinationPoint; // based of direction
	
	//  easting stuff
	CFood m_EatingFood;
	int m_nEatLoopCounter;
	
	// sleep Z's
	_AnimationTextureLink m_ZeeAnimation;
	CRectangle m_ZeeRect;
	
	
	private void DeterminNextAction()
	{
		// determin the new thing to do
		int decision = m_random.nextInt() & 0x1; // walk, idle
		NyanimalStates newState = (decision == 0?NyanimalStates.WALKING:NyanimalStates.IDLE);
		Direction newDirection = Direction.GetDirectionByValue(m_random.nextInt());
		
		// first time is always explicit
		if(m_curState == NyanimalStates.TOTAL)
		{
			newState = NyanimalStates.IDLE;
		}
		
		if(newState == NyanimalStates.IDLE)
		{
			m_timeInState = m_random.nextFloat() * 5.0f + 5.0f;
		}
		if(newState == NyanimalStates.WALKING)
		{
			// keep object creating outside of loops			
			int minX = (int)m_scale[0];
			int maxX = m_nScreenWidth - (int)m_scale[0];
			int minY = (int)m_scale[1];
			int maxY = m_nScreenHeight - (int)m_scale[1];
			int[] projPos = getPosition();
			int newPositionOffset = 0;
			
			// keep going until we get a good position
			boolean bAction = false;
			do
			{
				// determine the new position
				projPos = getPosition();
				newPositionOffset = m_random.nextInt(130) + 20;
				
				switch(newDirection)
				{
				case UP:
					projPos[1] -= newPositionOffset;
					m_destinationPoint = projPos[1];
					break; 
				case DOWN:
					projPos[1] += newPositionOffset;
					m_destinationPoint = projPos[1];
					break;
				case LEFT:
					projPos[0] -= newPositionOffset;
					m_destinationPoint = projPos[0];
					
					m_bMirror = false;
					break; 
				case RIGHT:
					projPos[0] += newPositionOffset;
					m_destinationPoint = projPos[0];

					m_bMirror = true;
					break;
				}
				
				// check if the new position is valid					
				if((projPos[0] >= minX) && (projPos[1] >= minY) && (projPos[0] <= maxX) && (projPos[1] <= maxY))
				{
					bAction = true;
				}
				else
				{
					newDirection = Direction.GetDirectionByValue(newDirection.ordinal() + 1);
				}
				
			} while(bAction == false);
		}
		
		ChangeState(newState, newDirection);
	}
	
	// ////////////////////////////////////////////////////////
	// ////////////////////////////////////////////////////////

	public CNyanimal()
	{
		super();
		m_Animations = new Hashtable<NyanimalAnimations, _AnimationTextureLink>();
		m_random = new Random();
	}

	@Override
	public Object clone()
	{
		CNyanimal ret = null;
		ret = (CNyanimal)super.clone();
		
		// state-related
		ret.m_curState = m_curState;
		ret.m_curStatus = m_curStatus;
		ret.m_curStage = m_curStage;
		ret.m_birthDay = (Date)m_birthDay.clone();
		ret.m_curAge = m_curAge;		// in days
		ret.m_happyMeter = m_happyMeter;	// 100 is happy,	 0 is unhappy,		// -100 mean dead
		ret.m_healthMeter = m_healthMeter;	// 100 is healthy,	 0 is unhealthy,	// medicine brings to 100
		ret.m_hungerMeter = m_hungerMeter;	// 100 is full,		 0 is hungry,		// goes down slower while sleeping
		ret.m_tiredMeter = m_tiredMeter;	// 100 is sleepy, 	 0 is wide awake,	// sleep brings to 100
		ret.m_boredMeter = m_boredMeter;	// 100 is bored,	 0 is content,		

		// game-related
		ret.m_random = new Random();
		ret.m_Animations = new Hashtable<NyanimalAnimations, _AnimationTextureLink>(m_Animations);
		ret.m_curAnimation = m_curAnimation;
		ret.m_curDirection = m_curDirection;
		ret.m_walkSpeed = m_walkSpeed;
		ret.m_timeInState = m_timeInState;
		ret.m_destinationPoint = m_destinationPoint; // based of direction
		
		
		return ret;
	}
	
	public void Init(NyanimalGLSurfaceView game)
	{
		Log.d(TAG, "Nyanimal Init Start");
		super.Init(game);
				
		float fRenderTime = 1.0f / 24.0f; //24 fps
		float f20FPS = 1.0f / 20.0f;
		float f18FPS = 1.0f / 18.0f;
				
		// cattage idle
		_AnimationTextureLink idleLink = new _AnimationTextureLink();
		idleLink.Create(game, "idle", R.drawable.cattagesprite2, 128, 128, 5, 5, fRenderTime, true);
		m_Animations.put(NyanimalAnimations.IDLE, idleLink);
		
		// cattage eat
		_AnimationTextureLink eatLink = new _AnimationTextureLink();	
		eatLink.Create(game, "eat", R.drawable.cattagebite, 128, 128, 4, 4, fRenderTime, true);
		m_Animations.put(NyanimalAnimations.EATING, eatLink);
		
		// cattage sleep
		_AnimationTextureLink sleepLink = new _AnimationTextureLink();
		sleepLink.Create(game, "sleep", R.drawable.cattagesnooze, 128, 128, 5, 5, f20FPS, true);
		m_Animations.put(NyanimalAnimations.SLEEPING, sleepLink);
		
		// the missing
		m_Animations.put(NyanimalAnimations.WALKING_UP, idleLink);
		m_Animations.put(NyanimalAnimations.WALKING_DOWN, idleLink);
		m_Animations.put(NyanimalAnimations.WALKING_LEFT, idleLink);
		m_Animations.put(NyanimalAnimations.WALKING_RIGHT, idleLink);
		m_Animations.put(NyanimalAnimations.HUNGRY, idleLink);
		m_Animations.put(NyanimalAnimations.SLEEPY, idleLink);
		m_Animations.put(NyanimalAnimations.BORED, idleLink);
		m_Animations.put(NyanimalAnimations.SICK, idleLink);
		m_Animations.put(NyanimalAnimations.DEAD, idleLink);
		
		// set the Zees
		m_ZeeAnimation = new _AnimationTextureLink();
		m_ZeeAnimation.Create(game, "zzzzz", R.drawable.zzzzz, 128, 128, 4, 4, f18FPS, true);
		m_ZeeRect = new CRectangle();
		m_ZeeRect.Init(game);
		m_ZeeRect.SetTexture(m_ZeeAnimation.GetTexture());
		m_ZeeRect.SetSize(128, 128);
		m_ZeeRect.SetDraw(false);
		
		// set the shadow
		m_Shadow = new CRectangle();
		m_Shadow.Init(game);
		CTexture shadowTexture = CTextureManager.GetInstance().LoadTexture(R.drawable.shadow, game);
		m_Shadow.SetTexture(shadowTexture);
		m_Shadow.SetSize();
		
		
		// default speed
		m_walkSpeed = 100.0f;
		
		// default position 
		SetPosition((int)(0.5f * game.getWidth()), (int)(0.75f * game.getHeight()));
		m_Shadow.SetPosition((int)(0.5f * game.getWidth()), (int)(0.75f * game.getHeight()));
		m_ZeeRect.SetPosition((int)(0.5f * game.getWidth()), (int)(0.75f * game.getHeight()));
				
		// load in the Nyanimal
		LoadNyanimal();
		
		DeterminNextAction();
		Log.d(TAG, "Nyanimal Init End");
	}
	
	private void LoadNyanimal()
	{
		Log.d(TAG, "Nyanimal Load Start");
		m_curState = NyanimalStates.TOTAL; // is total now so first DeterminNextAction() call doesnt end in infinite loop if it doesnt land on idle
		m_curStatus = NyanimalStatus.HAPPY;
		m_curStage = NyanimalStages.EGG;
		m_curAge = 0;					
		m_happyMeter = 100;	
		m_healthMeter = 100;	
		m_hungerMeter = 100;	
		m_tiredMeter = 100;	
		m_boredMeter = 100;	
		m_birthDay = new Date(1992, 03, 16);
		Log.d(TAG, "Nyanimal Load End");
	}
	
	/*
	 * 
	enum NyanimalStates { IDLE, EATING, SLEEPING, WALKING, TOTAL };
	enum NyanimalStatus { HAPPY, HUNGRY, SLEEPY, BORED, SICK, DEAD }; // determines different "idle" animations
	 * 
	 * */
	public void ChangeState(NyanimalStates state)
	{
		ChangeState(state, null);
	}
	public void ChangeState(NyanimalStates state, Direction dir)
	{
		Log.d(TAG, "Changing to state: " + state + " facing: " + dir);
		m_curState = state;
		
		if(dir == null)
		{
			dir = m_curDirection;
		}
		m_curDirection = dir;
				
		// determine our animation
		NyanimalAnimations newAnim = null;
		switch(m_curState)
		{
		case IDLE:
			switch(m_curStatus)
			{
				case HAPPY:				
					newAnim = NyanimalAnimations.IDLE;	
				break;
				case HUNGRY:
					newAnim = NyanimalAnimations.HUNGRY;
				break;
				case SLEEPY:
					newAnim = NyanimalAnimations.SLEEPY;
				break;
				case BORED:
					newAnim = NyanimalAnimations.BORED;
				break;
				case SICK:
					newAnim = NyanimalAnimations.SICK;
				break;
				case DEAD:
					newAnim = NyanimalAnimations.DEAD;
				break;
			}
			break;
		case EATING:
			newAnim = NyanimalAnimations.EATING;
			break;
		case SLEEPING:
			newAnim = NyanimalAnimations.SLEEPING;
			m_ZeeRect.SetDraw(true);
			break;
		case WALKING:
			switch(m_curDirection)
			{
			case UP:
				newAnim = NyanimalAnimations.WALKING_UP;
				break;
			case DOWN:
				newAnim = NyanimalAnimations.WALKING_DOWN;
				break;
			case LEFT:
				newAnim = NyanimalAnimations.WALKING_LEFT;
				break;
			case RIGHT:
				newAnim = NyanimalAnimations.WALKING_RIGHT;
				break;
			};
			break;
		};
		
		// ensure the animation doesnt start over
		if(newAnim != m_curAnimation)
		{
			m_Animations.get(newAnim).m_Animation.Reset();
		}
		m_curAnimation = newAnim;
		UpdateUV(m_Animations.get(m_curAnimation), m_bMirror);
	}

	@Override
	public void Update(float deltaTime)
	{
		// change bars where needed
		
		if(m_curState != NyanimalStates.EATING)
		{
			m_hungerMeter -= 1.0f * deltaTime;
			if(m_hungerMeter < 0)
			{
				m_hungerMeter = 0.0f;
			}
			
			if(m_curState != NyanimalStates.SLEEPING)
			{
				m_tiredMeter -= 0.25f * deltaTime;
				
				if(m_tiredMeter < 0)
				{
					ChangeState(NyanimalStates.SLEEPING);
				}
			}
		}
		
		// handle what to do in each state
		switch(m_curState)
		{
		case IDLE:
			m_timeInState -= deltaTime;
			if(m_timeInState < 0.0f)
			{
				DeterminNextAction();
			}
			break;
		case WALKING:
			int[] curPos = getPosition();
			switch(m_curDirection)
			{
			case UP:
				curPos[1] -= m_walkSpeed * deltaTime;
				if(curPos[1] < m_destinationPoint)
				{
					DeterminNextAction();
				}
				break; 
			case DOWN:
				curPos[1] += m_walkSpeed * deltaTime;
				if(curPos[1] > m_destinationPoint)
				{
					DeterminNextAction();
				}
				break;
			case LEFT:
				curPos[0] -= m_walkSpeed * deltaTime;
				if(curPos[0] < m_destinationPoint)
				{
					DeterminNextAction();
				}
				break; 
			case RIGHT:
				curPos[0] += m_walkSpeed * deltaTime;
				if(curPos[0] > m_destinationPoint)
				{
					DeterminNextAction();
				}
				break;
			}
			SetPosition(curPos[0], curPos[1]);
			break;
		case SLEEPING:
			m_tiredMeter += 1.0f * deltaTime;
			if(m_tiredMeter > GetMeterMax())
			{
				// no sleep
				m_ZeeRect.SetDraw(false);
				m_tiredMeter = GetMeterMax();
				DeterminNextAction();
			}

			boolean newFrame = m_ZeeAnimation.m_Animation.Update(deltaTime);
			m_ZeeRect.SetPosition((int)m_position[0], ((int)m_position[1]));
			m_ZeeRect.Update(deltaTime);
			
			// check if we have new UVs
			if(newFrame == true)
			{
				m_ZeeRect.UpdateUV(m_ZeeAnimation, m_bMirror);
			}
			
			break;
		case EATING:
			int nCurLoopCount = m_Animations.get(m_curAnimation).m_Animation.GetLoopCount();
			
			if(m_nEatLoopCounter != nCurLoopCount)
			{
				// take a bite
				m_nEatLoopCounter = nCurLoopCount;
				float fBiteAmount = m_EatingFood.TakeBite(25.0f);
				
				if(fBiteAmount > 0)
				{
					m_hungerMeter += fBiteAmount;
					
					// stop if full
					if(m_hungerMeter > GetMeterMax())
					{
						Log.d(TAG, "Full!");
						m_hungerMeter = GetMeterMax();
						GrabFood(null);
					}
				}
				else					
				{
					// no more food
					Log.d(TAG, "Food Gone!");
					GrabFood(null);
				}
			}			
			
			break;
		};		
		
		UpdateAnimationFrame(deltaTime);

		int[] frameSize = m_Animations.get(m_curAnimation).m_Animation.GetCurrentFrameSize();
		SetSize(frameSize[0], frameSize[1]);
		
		if(m_Shadow != null)
		{
			int m_nShadowOffset = 128;
			m_Shadow.SetPosition((int)m_position[0], ((int)m_position[1]) + m_nShadowOffset);
			m_Shadow.Update(deltaTime);
		}
	}

	private void UpdateAnimationFrame(float deltaTime)
	{
		boolean newFrame = m_Animations.get(m_curAnimation).m_Animation.Update(deltaTime);
		
		// check if we have new UVs
		if(newFrame == true)
		{
			UpdateUV(m_Animations.get(m_curAnimation), m_bMirror);
		}
	}

	@Override
	public void Draw(CArgumentGroup arg)
	{
		if(m_Shadow != null)
		{
			m_Shadow.Draw(arg);
		}
		
		super.Draw(arg);
		
		if(m_ZeeRect != null)
		{
			m_ZeeRect.Draw(arg);
		}
	}
	
	public void GrabFood(CFood food)
	{
		// pick up
		if(food != null)
		{
			food.PickedUp(this);
			food.SetBiteAmmount(5.0f);
			m_nEatLoopCounter = 0;
			ChangeState(NyanimalStates.EATING);
		}
		// put dowm
		else
		{
			m_EatingFood.PickedUp(null);
			DeterminNextAction();
		}
		m_EatingFood = food;
	}

	@Override
	public void HandleCollision(INyanObject other, RectF overlap)
	{
		super.HandleCollision(other, overlap);
		
		switch(other.GetType())
		{
		case FOOD:
			CFood food = (CFood)other;
			if(food.StoppedDragging() == true && m_EatingFood == null)
			{
				GrabFood(food);
			}
			break;
		default:
			break;
		};
		
	}

	@Override
	public Type GetType() 
	{
		// TODO Auto-generated method stub
		return Type.NYANIMAL;
	}
    
	
}
